#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <LittleFS.h>
#include <ESP8266WebServer.h>
#include <Ticker.h>
#include <EEPROM.h>

// 定义存储地址范围（共 512 字节）
#define EEPROM_SIZE 512
#define BASE_ADDRESS 0                    // 起始地址
#define WEAR_COUNT_ADDR (EEPROM_SIZE - 1) // 最后一个字节记录擦写次数
uint8_t eeprom_data = 0;

// Macro for printing with line number
#ifdef LIGHT_DEBUG
#define DEBUG_PRINT_LINE(msg) \
  Serial.print("[");          \
  Serial.print(__LINE__);     \
  Serial.print("] ");         \
  Serial.println(msg)
#else
#define DEBUG_PRINT_LINE(msg) send_dbg_to_mqtt(msg);
#endif

#define RELAY_DELAY_S (2 * 60)

#define LED_PIN 2
#ifndef LIGHT_DEBUG
#define IR_PIN 3
#endif

#define LIGHT_ON_VAL 1
#define RELAY_PIN 0

#define LIGHT_ON                         \
  digitalWrite(RELAY_PIN, LIGHT_ON_VAL); \
  publish_light_state("ON");             \
  DEBUG_PRINT_LINE("LIGHT ON\n")

#define LIGHT_OFF                         \
  digitalWrite(RELAY_PIN, !LIGHT_ON_VAL); \
  publish_light_state("OFF");             \
  DEBUG_PRINT_LINE("LIGHT OFF\n")

#define LED_WIFI_CONNECT_T 1
#define LED_HA_CTL_T 2
#define LED_IR_CTL_T 3
#define LED_NORMAL_T 4

#define SET_LED_STATE(period) \
  led_flash_cnt = 0;          \
  target_flash_cnt = period

// WiFi and MQTT configuration variables
char wifiSSID[32];
char wifiPassword[32];
char mqttServer[32];
int mqttPort;
char mqttUser[32];
char mqttPassword[32];

// Web server object
ESP8266WebServer server(80);

// MQTT client object
WiFiClient espClient;
PubSubClient client(espClient);

// 设备唯一标识（必须为英文）
const char *device_id = "esp8266_corridor_light";
const char *light_name = "走廊灯"; // 中文设备名称

// MQTT 主题定义
const char *light_config_topic = "homeassistant/switch/corridor_light/config";
const char *light_state_topic = "homeassistant/switch/corridor_light/state";
const char *light_command_topic = "homeassistant/switch/corridor_light/set";

const char *light_ir_name = "热释电传感器"; // 中文设备名称
const char *light_ir_stateTopic = "homeassistant/binary_sensor/light_ir_sensor/state";
const char *light_ir_configTopic = "homeassistant/binary_sensor/light_ir_sensor/config";

bool last_ir_state = 0;
bool ir_trigger = 0;
int delay_cnt = 0;
int led_flash_cnt, target_flash_cnt;
bool auto_ctrl = 1;
bool ir_detect_req = 0;

// 密码变量
char adminPassword[32] = "admin";
bool passwordChanged = false;

Ticker ir_detect_ticker;
Ticker led_ticker;

// 函数前向声明
void loadConfig();
void saveConfig();
void startWebServer();
void connectToWiFi();
void connectToMQTT();
void callback(char *topic, byte *payload, unsigned int length);
void publish_light_state(const char *state);
void publish_light_ir_state(bool h_l);
void publish_discovery();
void ir_detect_process();
void led_flash();
void send_dbg_to_mqtt(const char *msg);
bool read_ir_state();

void startWebServer()
{
  WiFi.mode(WIFI_AP);
  // Start Access Point
  WiFi.softAP("ESP8266_Config", "password");
  IPAddress ip = WiFi.softAPIP();
  DEBUG_PRINT_LINE("AP IP address: ");
  Serial.println(ip);

  server.on("/", HTTP_GET, []()
            {
    if (!server.authenticate(adminPassword, adminPassword))
    {
      return server.requestAuthentication();
    }
    String html = "<html><head>";
    html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
    html += "<style>";
    html += "body { font-family: Arial, sans-serif; margin: 20px; padding: 0; background-color: #f4f4f9; }";
    html += ".container { max-width: 400px; margin: auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); }";
    html += "h1 { text-align: center; color: #333; }";
    html += "form { display: flex; flex-direction: column; }";
    html += "label { margin-bottom: 5px; font-weight: bold; }";
    html += "input[type='text'], input[type='password'], input[type='number'] { padding: 10px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 4px; }";
    html += "input[type='submit'] { background-color: #28a745; color: white; padding: 10px; border: none; border-radius: 4px; cursor: pointer; }";
    html += "input[type='submit']:hover { background-color: #218838; }";
    html += "</style>";
    html += "</head><body>";
    html += "<div class='container'>";
    html += "<h1>ESP8266 Configuration</h1>";
    html += "<form method='POST' action='/save'>";
    html += "<label for='ssid'>WiFi SSID:</label>";
    html += "<input type='text' id='ssid' name='ssid'><br>";
    html += "<label for='password'>WiFi Password:</label>";
    html += "<input type='password' id='password' name='password'><br>";
    html += "<label for='mqttServer'>MQTT Server:</label>";
    html += "<input type='text' id='mqttServer' name='mqttServer'><br>";
    html += "<label for='mqttPort'>MQTT Port:</label>";
    html += "<input type='number' id='mqttPort' name='mqttPort'><br>";
    html += "<label for='mqttUser'>MQTT User:</label>";
    html += "<input type='text' id='mqttUser' name='mqttUser'><br>";
    html += "<label for='mqttPassword'>MQTT Password:</label>";
    html += "<input type='password' id='mqttPassword' name='mqttPassword'><br>";
    html += "<label for='adminPassword'>Admin Password:</label>";
    html += "<input type='password' id='adminPassword' name='adminPassword'><br>";
    html += "<input type='submit' value='Save'>";
    html += "</form></div></body></html>";
    server.send(200, "text/html", html); });

  server.on("/save", HTTP_POST, []()
            {
              char adminPassword_tmp[32] = "admin";
              if (!server.authenticate(adminPassword, adminPassword))
              {
                return server.requestAuthentication();
              }
              strcpy(wifiSSID, server.arg("ssid").c_str());
              strcpy(wifiPassword, server.arg("password").c_str());
              strcpy(mqttServer, server.arg("mqttServer").c_str());
              mqttPort = server.arg("mqttPort").toInt();
              strcpy(mqttUser, server.arg("mqttUser").c_str());
              strcpy(mqttPassword, server.arg("mqttPassword").c_str());
              strcpy(adminPassword_tmp, server.arg("adminPassword").c_str());

              if (strcmp(adminPassword_tmp, "") == 0)
              {
                if(!passwordChanged){ //如果密码是空的且没有修改密码，则报错。否则不影响原密码
                  server.send(400, "text/plain", "Admin password cannot be empty.");
                }
              } else{
                strcpy(adminPassword, adminPassword_tmp);
                passwordChanged = true;
              }

              saveConfig();
              server.sendHeader("Location", "/");
              server.send(303);
              ESP.restart(); });

  server.begin();
  DEBUG_PRINT_LINE("Web server started");
}

void connectToWiFi()
{
  uint8_t check_connect_cnt;
  SET_LED_STATE(LED_WIFI_CONNECT_T);
  do
  {
    WiFi.begin(wifiSSID, wifiPassword);
    DEBUG_PRINT_LINE("connectToWiFi:");
    Serial.print(wifiSSID);
    check_connect_cnt = 0;
    while (WiFi.status() != WL_CONNECTED)
    {
      delay(1000);
      Serial.print(".");
      check_connect_cnt++;
      if (check_connect_cnt > 20)
      {
        break; // 超过20秒没有连则重连
      }
    }

    if (check_connect_cnt <= 20)
    {
      DEBUG_PRINT_LINE("connected");
    }

  } while (WiFi.status() != WL_CONNECTED); // 一直重连，直到连上为止
  SET_LED_STATE(LED_NORMAL_T);
}
void saveConfig()
{
  File configFile = LittleFS.open("/config.txt", "w");
  if (!configFile)
  {
    DEBUG_PRINT_LINE("Failed to open config file for writing");
    return;
  }

  configFile.printf("%s\n%s\n%s\n%d\n%s\n%s\n%s\n%d",
                    wifiSSID, wifiPassword,
                    mqttServer, mqttPort,
                    mqttUser, mqttPassword,
                    adminPassword, passwordChanged ? 1 : 0);

  configFile.close();
  DEBUG_PRINT_LINE("Config saved");
}

void loadConfig()
{
  File configFile = LittleFS.open("/config.txt", "r");
  if (!configFile)
  {
    DEBUG_PRINT_LINE("Failed to open config file for reading");
    return;
  }

  configFile.readBytesUntil('\n', wifiSSID, sizeof(wifiSSID));
  configFile.readBytesUntil('\n', wifiPassword, sizeof(wifiPassword));
  configFile.readBytesUntil('\n', mqttServer, sizeof(mqttServer));
  mqttPort = configFile.parseInt();           // Use parseInt to read integer
  configFile.seek(configFile.position() + 1); // Move past the newline character
  configFile.readBytesUntil('\n', mqttUser, sizeof(mqttUser));
  configFile.readBytesUntil('\n', mqttPassword, sizeof(mqttPassword));
  configFile.readBytesUntil('\n', adminPassword, sizeof(adminPassword));
  passwordChanged = configFile.parseInt() == 1;

  configFile.close();
  DEBUG_PRINT_LINE("Config loaded");

  // Print loaded configuration for debugging
  DEBUG_PRINT_LINE("Loaded WiFi SSID: ");
  Serial.println(wifiSSID);
  DEBUG_PRINT_LINE("Loaded WiFi Password: ");
  Serial.println(wifiPassword);
  DEBUG_PRINT_LINE("Loaded MQTT Server: ");
  Serial.println(mqttServer);
  DEBUG_PRINT_LINE("Loaded MQTT Port: ");
  Serial.println(mqttPort);
  DEBUG_PRINT_LINE("Loaded MQTT User: ");
  Serial.println(mqttUser);
  DEBUG_PRINT_LINE("Loaded MQTT Password: ");
  Serial.println(mqttPassword);
  DEBUG_PRINT_LINE("Loaded Admin Password: ");
  Serial.println(adminPassword);
  DEBUG_PRINT_LINE("Password Changed: ");
  Serial.println(passwordChanged ? "true" : "false");

  // Print strlen of each configuration for debugging
  DEBUG_PRINT_LINE("strlen(WiFi SSID): ");
  Serial.println(strlen(wifiSSID));
  DEBUG_PRINT_LINE("strlen(WiFi Password): ");
  Serial.println(strlen(wifiPassword));
  DEBUG_PRINT_LINE("strlen(MQTT Server): ");
  Serial.println(strlen(mqttServer));
  DEBUG_PRINT_LINE("strlen(MQTT User): ");
  Serial.println(strlen(mqttUser));
  DEBUG_PRINT_LINE("strlen(MQTT Password): ");
  Serial.println(strlen(mqttPassword));
  DEBUG_PRINT_LINE("strlen(Admin Password): ");
  Serial.println(strlen(adminPassword));
}

// 回调函数：处理 MQTT 指令
void callback(char *topic, byte *payload, unsigned int length)
{
  String message;
  for (size_t i = 0; i < length; i++)
  {
    message += (char)payload[i];
  }

  if (String(topic) == light_command_topic)
  {
    if (message == "ON")
    {
      auto_ctrl = 0;
      LIGHT_ON;
      DEBUG_PRINT_LINE("light on for ha ctrl");
      SET_LED_STATE(LED_HA_CTL_T);
    }
    else if (message == "OFF")
    {
      LIGHT_OFF;
      DEBUG_PRINT_LINE("light off for ha ctrl");
      auto_ctrl = 1;
      SET_LED_STATE(LED_NORMAL_T);
    }
  }
  else if (String(topic) == "zoulang_light_cfg")
  {
    if (message == "RESTART")
    {
      ESP.restart();
    }
    /*
    * {
        "ssid": "wifi_id",
        "password": "pass"
    * }
    */
  }
}

// 发布设备状态到 MQTT
void publish_light_state(const char *state)
{
  client.publish(light_state_topic, state, true); // 更新 HA 中的设备状态
  Serial.println("update ha light state");
  Serial.println(state);
}
void publish_light_ir_state(bool h_l)
{
  client.publish(light_ir_stateTopic, h_l ? "ON" : "OFF", true); // 更新 HA 中的设备状态
  Serial.println("update ha ir state");
  Serial.println(h_l ? "ON" : "OFF");
}

// 连接 MQTT 服务器
void connectToMQTT()
{
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
  DEBUG_PRINT_LINE("connect mqtt");
  Serial.print(mqttServer);
  Serial.print(":");
  Serial.print(mqttPort);

  int retry_count = 0;
  client.connect("ESP8266Client", mqttUser, mqttPassword);
  do
  {
    Serial.print("MQTT connection failed, retrying in 5 seconds...");
    delay(2000);
    retry_count++;
    if (((retry_count + 1) % 2) == 0)
    {
      client.connect("ESP8266Client", mqttUser, mqttPassword);
    }
    if (retry_count > 20)
    {
      break; // mqtt的重试需要再限定时间内完成，因为有可能是wifi断了，不能一直等在这
    }

  } while (!client.connected());
  if (retry_count > 20)
  {
    Serial.println("can not connect to mqtt server");
  }
  else
  {
    Serial.println("MQTT connected");
    client.subscribe(light_command_topic); // 订阅控制指令主题
    client.subscribe("zoulang_light_cfg");
  }
}

// 发送 MQTT 自动发现消息（作为开关设备）
void publish_discovery()
{
  String light_config = "{"
                        "\"name\":\"" +
                        String(light_name) + "\","
                                             "\"unique_id\":\"" +
                        String(device_id) + "\","
                                            "\"command_topic\":\"" +
                        String(light_command_topic) + "\","
                                                      "\"state_topic\":\"" +
                        String(light_state_topic) + "\","
                                                    "\"icon\": \"mdi:lightbulb\","
                                                    "\"device\":{"
                                                    "\"identifiers\":[\"" +
                        String(device_id) + "\"],"
                                            "\"name\":\"" +
                        String(light_name) + "\","
                                             "\"manufacturer\":\"ESP8266\","
                                             "\"model\":\"ESP-01\""
                                             "}"
                                             "}";
  String light_ir_config = "{"
                           "\"name\":\"" +
                           String(light_ir_name) + "\","
                                                   "\"unique_id\":\"" +
                           String(device_id) + "\","
                                               //                    "\"command_topic\":\"" +
                                               // String(light_command_topic) + "\","
                                               "\"state_topic\":\"" +
                           String(light_ir_stateTopic) + "\","
                                                         "\"icon\": \"mdi:motion-sensor\","
                                                         "\"device\":{"
                                                         "\"identifiers\":[\"" +
                           String(device_id) + "\"],"
                                               "\"name\":\"" +
                           String(light_name) + "\","
                                                "\"manufacturer\":\"ESP8266\","
                                                "\"model\":\"ESP-01\""
                                                "}"
                                                "}";

  client.publish(light_config_topic, light_config.c_str(), true);      // 发送自动发现消息
  client.publish(light_ir_configTopic, light_ir_config.c_str(), true); // 发送自动发现消息
}

#ifndef LIGHT_DEBUG
void ir_detect_process() // 该函数在定时中断执行，不受wifi和mqtt重连任务的影响
{
  bool cur_ir_state;
  if (auto_ctrl) // 如果通过手机控制了灯，则取消自动检测，直到手机控制关灯
  {
    cur_ir_state = read_ir_state();
    if (last_ir_state != cur_ir_state)
    {
      last_ir_state = cur_ir_state;
      publish_light_ir_state(last_ir_state);
      if (cur_ir_state == 1)
      {
        ir_trigger = 1;
        LIGHT_ON;
        DEBUG_PRINT_LINE("ir trigger on");
      }
    }

    if (ir_trigger)
    {
      SET_LED_STATE(LED_IR_CTL_T);
      delay_cnt = delay_cnt + 1;
      if (delay_cnt > RELAY_DELAY_S)
      {
        delay_cnt = 0;
        ir_trigger = 0;
        LIGHT_OFF;
        DEBUG_PRINT_LINE("ir trigger off");
        SET_LED_STATE(LED_NORMAL_T);
      }
    }
  }
}
#endif

void led_flash()
{
  led_flash_cnt++;
  if (target_flash_cnt == led_flash_cnt)
  {
    led_flash_cnt = 0;
    digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  }
}
void send_dbg_to_mqtt(const char *msg)
{
  client.publish("zoulang_light_debug", msg, true);
}

bool read_ir_state()
{
  bool ir_state;
  pinMode(IR_PIN, INPUT_PULLUP);
  ir_state = digitalRead(IR_PIN);
  Serial.begin(115200);

  return ir_state;
}

void setup()
{
  pinMode(RELAY_PIN, OUTPUT);
  LIGHT_OFF;
  // 获取复位信息
  rst_info *resetInfo = system_get_rst_info();
  // delay(8000);
  Serial.begin(115200);
  // Serial.println(resetInfo->reason);
  if (resetInfo->reason == REASON_EXT_SYS_RST)
  {
    EEPROM.begin(EEPROM_SIZE);
    // 读取当前擦写次数
    uint8_t wear_count = EEPROM.read(WEAR_COUNT_ADDR);

    // 计算当前写入地址（轮换地址）
    uint8_t current_address = (wear_count % (EEPROM_SIZE - 1)) + BASE_ADDRESS;
    uint8_t next_address = ((wear_count + 1) % (EEPROM_SIZE - 1)) + BASE_ADDRESS;
    eeprom_data = EEPROM.read(current_address);

    if (eeprom_data)
    {
      LIGHT_ON;
      auto_ctrl = 0;
      // pinMode(LED_PIN, OUTPUT);
      // digitalWrite(LED_PIN, 0);
    }
    else
    {
      LIGHT_OFF;
      auto_ctrl = 1;
      // pinMode(LED_PIN, OUTPUT);
      // digitalWrite(LED_PIN, 1);
    }
    eeprom_data = !eeprom_data;
    // 写入 1bit 数据（0 或 1）
    EEPROM.write(next_address, eeprom_data);
    EEPROM.write(WEAR_COUNT_ADDR, wear_count + 1); // 更新擦写次数
    EEPROM.commit();                               // 提交数据到 Flash
  }
  delay(4000);
  ir_detect_ticker.attach(1, ir_detect_process); // 每隔1秒检测一下红外传感器信号
  led_ticker.attach_ms(500, led_flash);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, HIGH); // 初始关闭 LED

#ifdef WEB_CFG_SUPPORT
  // Initialize LittleFS
  if (!LittleFS.begin())
  {
    DEBUG_PRINT_LINE("Failed to mount LittleFS");
    return;
  }
  DEBUG_PRINT_LINE("Mounted LittleFS");

  // Load configuration from LittleFS
  loadConfig();

  // Check if configuration is empty or if password needs change
  if (strlen(wifiSSID) == 0 || strlen(wifiPassword) == 0 ||
      strlen(mqttServer) == 0 || mqttPort <= 0 ||
      strlen(mqttUser) == 0 || strlen(mqttPassword) == 0 ||
      !passwordChanged)
  {
    DEBUG_PRINT_LINE("No valid configuration found or password not changed, starting AP...");
    startWebServer();
  }
  else
  {
#else
  strcpy(wifiSSID, "*");
  strcpy(wifiPassword, "*");
  strcpy(mqttServer, "*");
  mqttPort = 18860;
#endif
    WiFi.mode(WIFI_STA);
    connectToWiFi();
    delay(5000); // Wait for WiFi connection
    if (WiFi.status() == WL_CONNECTED)
    {
      DEBUG_PRINT_LINE("Connected to WiFi");
      connectToMQTT();
      client.setBufferSize(512); // 增加缓冲区大小以避免消息截断

      // 发送自动发现消息并发布初始状态
      publish_discovery();
      publish_light_state((digitalRead(RELAY_PIN) == LIGHT_ON_VAL) ? "ON" : "OFF");
    }
    else
    {
      DEBUG_PRINT_LINE("Failed to connect to WiFi");
    }
#ifdef WEB_CFG_SUPPORT
  }
#endif
}

void loop()
{
  if ((WiFi.getMode() == WIFI_STA) && WiFi.status() != WL_CONNECTED)
  {
    connectToWiFi();
  }
  else if ((WiFi.getMode() == WIFI_STA) && !client.connected()) // AP模式下不用检查mqtt连接。
  {
    // 保持 MQTT 连接
    connectToMQTT();
  }
  client.loop();
  server.handleClient();
}
